import json
from http import HTTPStatus
from pathlib import PurePosixPath
from urllib.parse import unquote, urlparse
import pyttsx3
import requests
from dashscope import ImageSynthesis
from moviepy import AudioFileClip, ImageClip, concatenate_videoclips, CompositeVideoClip
from moviepy.video.tools.subtitles import SubtitlesClip,TextClip
from openai import OpenAI
from dotenv import load_dotenv
import os
import asyncio

load_dotenv()

llm = OpenAI(
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    api_key=os.getenv("DASHSCOPE_API_KEY"),
)

class FairytaleVideo:
    def __init__(self):
        # self.audio_engine = pyttsx3.init(driverName='sapi5') # Windows（默认sapi5，可显式指定）
        self.text_llm = os.getenv("TEXT_MODEL")
        self.image_llm = os.getenv("IMAGE_MODEL")

    async def run(self, topic: str) -> None:
        # 1. 根据用户输入的主题生成童话故事
        fairytale = await self._generate_fairytale(topic) # 单独的_generate_fairytale会报错，只能是通过类或者子类self._generate_fairytale才能访问一个下划线的方法
        # # 2. 将童话故事分割成多个片段，
        sentences = await self._split_fairytale(fairytale)
        # with open("sentences.json", "r", encoding="utf-8") as file:
        #     sentences = json.load(file)
        print("sentences生成完毕",sentences)
        # 3. 为每个片段生成“文生图”的提示词
        prompts = await self._generate_prompt(sentences)
        # with open("prompts.json", "r", encoding="utf-8") as file:
        #     prompts = json.load(file)
        print("prompts提示词生成完成", prompts)
        # 4. 根据提示词生成图片
        await self._generate_images(prompts)
        # 5. 根据分割后的片段生成语音
        self._generate_audio2(sentences)
        # 6. 将图片、片段内容（字幕）、语音 合成一个视频
        await self._generate_video(sentences)

    async def _generate_fairytale(self, topic: str) -> str:
        response = llm.chat.completions.create(
            model=self.text_llm,
            messages=[
                {"role": "system", "content": "你是专业的童话故事大师，能够根据用户输入的主题创作出童话故事完整内容。如果用户输入的主题所涉及的故事在历史中存在过，那么就按照历史故事大纲来：否则就自行构造故事情节。"},
                {"role": "user", "content": f"帮我生成一个主题为：“{topic}”的童话故事完整内容。故事内容要尽量详细，不可省略剧情。只要返回故事内容，不要返回任何其他的额外信息"},
            ],
            # stream=True 流式输出
        )
        # ? 流式输出
        # for chunk in response:
        #     print(chunk.choices[0].delta.content, end="", flush=True)
        fairytale = response.choices[0].message.content
        return fairytale

    async def _split_fairytale(self, fairytale: str) -> list[str]:
        response = llm.chat.completions.create(
            model=self.text_llm,
            messages=[{
                "role": "user",
                "content": f"故事内容为：{fairytale},请按照故事的情节，将故事内容划分为多个片段，每个片段仅描述一件事，且宜短不宜长，然后将这些片段内容直接放到json格式的数组中返回。"
            }],
            response_format={"type": "json_object"}
        )
        sentences: list[str] = json.loads(response.choices[0].message.content)
        with open("sentences.json","w",encoding="utf-8") as f:
            json.dump(sentences, f, ensure_ascii=False)
        return sentences

    async def _generate_prompt(self, sentences: list[str]) -> list[str]:
        response = llm.chat.completions.create(
            model=self.text_llm,
            messages=[{
                "role": "system",
                "content": f"你是专业的“大模型文生图”的提示词生成专家。能够根据用户输入的故事片段，结合故事情节，为每个片段生成“文生图”提示词，提示词只描述场景，不描述故事内容，场景描述要尽量详细，包括场景中的人物形象、背景、为剧情服务而出现的元素等。要求生成的图片中的元素为童话风格，图片里的元素不要写实风格，角色在每个提示词中的描述应该整体保持一致！并且最后提示词以中文输出即可！"
            },{
                "role": "system",
                "content": f"用户输入的剧情片段为{sentences}，请为每个片段生成“文生图”提示词，并将这些提示词直接放到json格式的数组返回。"
            }],
            response_format={"type": "json_object"}
        )
        prompts = response.choices[0].message.content
        with open("prompts.json", "w", encoding="utf-8") as f:
            json.dump(prompts, f, ensure_ascii=False)
        return prompts

    async def _generate_images(self, prompts: list[str]) -> None:
        # 确保图片目录存在（避免文件创建失败）
        os.makedirs("images", exist_ok=True)
        for index, prompt in enumerate(prompts):
            while True:
                print(f'----生成第{index+1}张图片，提示词为{prompt}----')
                rsp = ImageSynthesis.call(api_key=os.getenv("DASHSCOPE_API_KEY"),
                                          model=self.image_llm,
                                          prompt=prompt+"图片风格为童话绘本类型",  # 正向提示词
                                          negative_prompt="画面的元素必须是童话动漫风格，不能是写实风格！",  # 反向提示词表示不希望生成的内容
                                          n=1,  # 生成一张图片
                                          size='1440*810',  # 图片大小
                                          # prompt_extend=True, # 是否开启prompt智能改写
                                          watermark=False,  # 是否添加水印标识，水印位于图片右下角，文案固定为“AI生成”。
                                          seed=12345)  # 随机数种子
                print("response: %s" % rsp)
                if rsp.status_code == HTTPStatus.OK:
                    # 在当前目录下保存图片
                    results = rsp.output.results
                    if len(results) > 0:
                        result = results[0]
                        file_name = PurePosixPath(unquote(urlparse(result.url).path)).parts[-1]
                        with open(f'images/{index+1}{os.path.splitext(file_name)[-1]}', 'wb+') as f:
                            f.write(requests.get(result.url).content)
                        break
                else:
                    print('sync_call Failed, status_code: %s, code: %s, message: %s' %
                          (rsp.status_code, rsp.code, rsp.message))

    def _generate_audio(self, sentences: list[str]) -> None:
        # 确保音频目录存在（避免文件创建失败）
        os.makedirs("audios", exist_ok=True)
        audio_engine = pyttsx3.init()  # driverName='sapi5' Windows（默认sapi5，可显式指定）
        # # 可选：调整引擎参数（如语速、音量）
        # audio_engine.setProperty('rate', 150)  # 语速（默认200，数值越小越慢）
        # audio_engine.setProperty('volume', 1.0)  # 音量（0.0-1.0）

        for index, sentence in enumerate(sentences):
            try:
                audio_engine.save_to_file(sentence, f'audios/{index+1}.mp3')
                print(f"开始生成第{index + 1}个音频：{sentence}")
                audio_engine.runAndWait()
                print(f"生成了第{index+1}个音频")
            except Exception as e:
                    print(f"❌ 第{index+1}个音频生成失败：{str(e)}")

        # 所有任务完成后再释放资源
        audio_engine.stop()


    def _generate_audio2(self, sentences: list[str]) -> None:
        # 确保音频目录存在（避免文件创建失败）
        os.makedirs("audios", exist_ok=True)

        for index, sentence in enumerate(sentences):
            try:
                tts = _TTS()
                tts.start(sentence,index)
                del tts
            except Exception as e:
                    print(f"❌ 第{index+1}个音频生成失败：{str(e)}")


    async def _generate_video(self, sentences: list[str]) -> None:
        # 1. 读取所有图片，将图片生成Cli
        image_clips = []
        subtitles = []
        subtitle_start_time = 0
        for index, sentence in enumerate(sentences):
            # 设置好图片和音频，每一张图片和音频绑定起来，图片展示时间等于音频持续时间
            audio_clip = AudioFileClip(f"audios/{index+1}.mp3")
            audio_duration = audio_clip.duration
            image_clip = ImageClip(f"images/{index+1}.png",duration=audio_duration)
            image_clip.audio = audio_clip # 图片音频等于我们传入的音频

            # 设置字字幕，字幕格式是((起始时间，结束时间)，字幕内容)
            if len(sentence) > 40: # 长度超过40换行
                sentence = sentence[:len(sentence)//2] + '\n' + sentence[len(sentence)//2:]
            subtitle = ((subtitle_start_time, subtitle_start_time + audio_duration), sentence)
            subtitles.append(subtitle)
            
            # 重新设置下一个字幕起始时间
            subtitle_start_time += audio_duration
            
            image_clips.append(image_clip)

        # 2. 所有图片的clip合成一个clip
        video_clip = concatenate_videoclips(image_clips)
        # 3. 生成字幕对象
        # ! 这里必须有字体文件font='fonts/AaHouLangXingKai-2.ttf'
        generator = lambda text: TextClip(text=text, font='./fonts/AaHouLangXingKai-2.ttf', font_size=30, color='white',
                                          stroke_width=2, stroke_color='black') # stroke_width字体描边宽度
        subtitle_clip = SubtitlesClip(subtitles, make_textclip=generator,encoding='utf-8')
        subtitle_clip.pos = lambda t: ("center", video_clip.h - 100) # 设置字幕位置

        # 4. 将字幕和图片组成的video_clip重叠在一起
        video = CompositeVideoClip([video_clip,subtitle_clip])
        video.write_videofile("output.mp4", fps=24) # fps表示帧
        print("视频制作完成！")



class _TTS:
    engine = None

    def __init__(self):
        self.engine = pyttsx3.init()
        self.engine.setProperty('rate', 150) # 语速（默认200，数值越小越慢）
        self.engine.setProperty('volume', 1.0)  # 音量（0.0-1.0）

    def start(self,sentence,index):
        self.engine.save_to_file(sentence, f'audios/{index + 1}.mp3')
        print(f"开始生成第{index + 1}个音频：{sentence}")
        self.engine.runAndWait()
        print(f"生成了第{index + 1}个音频")

async def main():
    generator = FairytaleVideo()
    print("模型加载完成")
    await generator.run("卖火柴的小女孩")

if __name__ == "__main__":
    asyncio.run(main())
